#include <stdint.h>
#include <assert.h>
#include "arena.h"
#include "vector.h"

#if defined(__GNUC__)
#   define memcpy __builtin_memcpy
#   define memmove __builtin_memmove
#else
#   if defined(_MSC_VER)
#		include <intrin.h>
#       pragma intrinsic(memcpy)
#   endif
#   include <string.h>
#endif /* defined(__GNUC__) */

struct vector* vector_create(arena_t* a, size_t typelen, size_t capacity)
{
#define DEFAULT_CAPACITY 16
    struct vector* v = NULL;
    assert(a);
    assert(typelen);
    capacity = capacity ? capacity : DEFAULT_CAPACITY;
    v = (struct vector*)arena_malloc(a
        , sizeof(struct vector) + typelen * capacity);
    if(v == NULL)
        return NULL;
    v->arena = a;
    v->capacity = capacity;
    v->length = 0;
    v->typelen = typelen,
    v->data = (void*)&v[1];
    return v;
#undef DEFAULT_CAPACITY
}

int vector_reserve(struct vector* v, size_t capacity)
{
    assert(v);
    if(capacity > v->capacity)
    {
        void* temp = v->data;
        v->data = arena_malloc(v->arena, v->typelen * capacity);
        if(v->data == NULL)
            return -1;
        v->capacity = capacity;
        if(v->length)
            memcpy(v->data, temp, v->typelen * v->length);
    }
    return 0;
}

static __inline void assign(struct vector* v, int index, void* p)
{
    switch(v->typelen)
    {
    case 1:
        ((uint8_t*)v->data)[index] = *((uint8_t*)p);
        break;
    case 2:
        ((uint16_t*)v->data)[index] = *((uint16_t*)p);
        break;
    case 4:
        ((uint32_t*)v->data)[index] = *((uint32_t*)p);
        break;
    case 8:
        ((uint64_t*)v->data)[index] = *((uint64_t*)p);
        break;
    case 16:
        ((uint64_t*)v->data)[index] = ((uint64_t*)p)[0];
        ((uint64_t*)v->data)[index + 1] = ((uint64_t*)p)[1];
        break;
    default:
        memcpy((unsigned char*)v->data + index * v->typelen, p, v->typelen);
    }
}

int vector_append(struct vector* v, void* p)
{
    assert(v);
    assert(p);
    if(v->length == v->capacity)
    {
        if(vector_reserve(v, v->capacity * 2) < 0)
            return -1;
    }
    assign(v, v->length, p);
    v->length++;
    return 0;
}

int vector_insert(struct vector* v, int index, void* p)
{
    size_t nbytes;
    assert(v);
    assert(index > 0 && (size_t)index < v->length);
    assert(p);
    nbytes = (v->length - index) * v->typelen;
    if(v->length == v->capacity)
    {
        if(vector_reserve(v, v->capacity * 2) < 0)
            return -1;
    }
    memmove((unsigned char*)v->data + (index + 1) * v->typelen
        , (unsigned char*)v->data + index * v->typelen, nbytes);
    assign(v, index, p);
    v->length++;
    return 0;
}

int vector_erase(struct vector* v, int index)
{
    size_t nbytes;
    assert(v);
    assert(index > 0 && (size_t)index < v->length);
    nbytes = (v->length - index - 1) * v->typelen;
    memmove((unsigned char*)v->data + index * v->typelen
        , (unsigned char*)v->data + (index + 1) * v->typelen, nbytes);
    v->length--;
    return 0;
}
